package com.biz.primus.ms.stock.dao.redis;

import com.biz.primus.base.redis.CrudRedisDao;
import com.biz.primus.base.redis.util.RedisUtil;
import com.biz.primus.common.enums.StockChannelEnum;
import com.biz.primus.model.stock.enums.StockChangeTypeEnum;
import com.biz.primus.ms.stock.dao.ro.Constants;
import com.biz.primus.ms.stock.dao.ro.DepotStockRo;
import com.google.common.collect.Maps;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.google.common.collect.Lists.newArrayList;

/**
 * DepotStockRedisDao
 * 门店库存DAO
 *
 * @author JKLiues
 * @date 2017/10/19
 */
@Repository
public class DepotStockRedisDao extends CrudRedisDao<DepotStockRo, String> {


    @Override
    public void save(DepotStockRo ro) {
        super.save(ro);
        this.doMappings(ro);
    }

    @Override
    public void save(Iterable<DepotStockRo> ros) {
        super.save(ros);
        ros.forEach(this::doMappings);
    }


    public List<DepotStockRo> getStocksByDepotCode(String depotCode) {
        String depotKey = this.getDepot2StockMappingKey(depotCode);
        Set<byte[]> smembers = super.smembers(depotKey);
        return findByIds(smembers);
    }

    public List<DepotStockRo> getStocksByProductCode(String poroductCode) {
        String key = this.getProduct2StockMappingKey(poroductCode);
        Set<byte[]> smembers = super.smembers(key);
        return findByIds(smembers);
    }

    /**
     * 根据门店编码&商品编码集批量查询
     *
     * @param channelCode  渠道码
     * @param depotCode    门店编码集
     * @param productCodes 商品编码集
     * @return 库存ROS
     */
    public List<DepotStockRo> getByDepotCodeAndProductCodes(StockChannelEnum channelCode, String depotCode, Collection<String> productCodes, boolean allowedNull) {
        if (StringUtils.isEmpty(depotCode) || CollectionUtils.isEmpty(productCodes)) {
            return new ArrayList<>();
        }
        List<String> ids = newArrayList();
        for (String productCode : productCodes) {
            ids.add(String.format("%s%s%s%s%s", channelCode, Constants.SEPARATOR, depotCode, Constants.SEPARATOR, productCode));
        }
        List<String> hashKeys = ids.stream().map(this::getHashKey).collect(Collectors.toList());
        List<Object> objects = super.pipeGet(hashKeys);
        if (allowedNull) {
            return objects.stream()
                    .map(o -> Objects.isNull(o) ? null : (DepotStockRo) o)
                    .collect(Collectors.toList());
        } else {
            return objects.stream()
                    .filter(Objects::nonNull)
                    .map(o -> (DepotStockRo) o)
                    .collect(Collectors.toList());
        }
    }

    /**
     * 根据门店编码集&商品编码集批量查询
     *
     * @param channelCode  渠道码
     * @param depotCodes   门店编码集
     * @param productCodes 商品编码集
     * @return 库存ROS结果集不包含null
     */
    public List<DepotStockRo> getByDepotCodesAndProductCodes(StockChannelEnum channelCode, Collection<String> depotCodes, Collection<String> productCodes) {
        if (CollectionUtils.isEmpty(depotCodes) || CollectionUtils.isEmpty(productCodes)) {
            return new ArrayList<>();
        }
        List<String> ids = newArrayList();
        for (String depotCode : depotCodes) {
            for (String productCode : productCodes) {
                ids.add(String.format("%s%s%s%s%s", channelCode, Constants.SEPARATOR, depotCode, Constants.SEPARATOR, productCode));
            }
        }
        List<String> hashKeys = ids.stream().map(this::getHashKey).collect(Collectors.toList());
        List<Object> objects = super.pipeGet(hashKeys);
        return objects.stream()
                .filter(Objects::nonNull)
                .map(o -> (DepotStockRo) o)
                .collect(Collectors.toList());
    }

    /**
     * 增量库存更新
     *
     * @param idToQuantity Paid<id & 数量>
     */

    public void updateDepotStockQuantityInBatch(Map<String, Integer> idToQuantity) {
        Map<String, Integer> hashKeyToQuantoty = Maps.newHashMap();
        if (MapUtils.isNotEmpty(idToQuantity)) {
            Set<String> ids = idToQuantity.keySet();
            for (String id : ids) {
                hashKeyToQuantoty.put(getHashKey(id), idToQuantity.get(id));
            }
        }
        super.pipeHincrBy(hashKeyToQuantoty, Constants.RO_QUANTITY_FIELD);
    }

    /**
     * 锁库存更新
     *
     * @param idToLockedQuantity Paid<id & 数量>
     */
    public void updateDepotLockedStockQuantityInBatch(Map<String, Integer> idToLockedQuantity) {
        Map<String, Integer> hashKeyToQuantity = Maps.newHashMap();
        if (MapUtils.isNotEmpty(idToLockedQuantity)) {
            Set<String> ids = idToLockedQuantity.keySet();
            for (String id : ids) {
                hashKeyToQuantity.put(getHashKey(id), idToLockedQuantity.get(id));
            }
        }
        super.pipeHincrBy(hashKeyToQuantity, Constants.RO_LOCKED_QUANTITY_FIELD);
    }

    /**
     * 获取全局库存版本
     *
     * @return version 版本
     */
    public Long getVersion(StockChannelEnum channelCode) {
        String key = getVersionMappingKey(channelCode);
        byte[] bytes = get(key);
        Long version = RedisUtil.byteArrayToLong(bytes);
        return Objects.isNull(version) ? 0L : version;
    }

    /**
     * 键值映射
     */
    private void doMappings(DepotStockRo ro) {
        String depotCode = ro.getDepotCode();
        String productCode = ro.getProductCode();
        String depotKey = this.getDepot2StockMappingKey(depotCode);
        String productKey = this.getProduct2StockMappingKey(productCode);
        this.sadd(depotKey, RedisUtil.toByteArray(ro.getId()));
        this.sadd(productKey, RedisUtil.toByteArray(ro.getId()));
    }

    public Boolean isBnEffective(String bn, StockChangeTypeEnum changeType) {
        String key = getStockChangedMappingKey(bn, changeType);
        byte[] bytes = this.get(key);
        return bytes == null;
    }

    /**
     * 设置全局库存版本
     *
     * @param channelCode 渠道码
     */
    public void updateVersion(StockChannelEnum channelCode) {
        String key = getVersionMappingKey(channelCode);
        incr(key);
    }

    /**
     * 获取库存版本号Redis key
     *
     * @param channelCode 渠道码
     * @return key
     */
    private String getVersionMappingKey(StockChannelEnum channelCode) {
        return getKeyByParams(VERSION, channelCode);
    }

    private String getDepot2StockMappingKey(String depotCode) {
        return getKeyByParams(DEPOT, depotCode);
    }

    private String getProduct2StockMappingKey(String productCode) {
        return getKeyByParams(PRODUCT, productCode);
    }

    private String getStockChangedMappingKey(String bn, StockChangeTypeEnum changeType) {
        return getKeyByParams(CHANGE, bn, changeType);
    }

    /**
     * 常量字符串version
     */
    private static final String VERSION = "version";
    private static final String DEPOT = "depot";
    private static final String PRODUCT = "product";
    private static final String CHANGE = "change";
}
